A computer application program usually includes a number of separate routines. Typically, the routines include a main program and several subsidiary routines referred to as objects, modules, or resources. Execution of the application program begins with the main program with calls being made to the subsidiary routines. In order to operate as a complete program, prior to execution these routines are linked together using a linker such as 386 link. The linker copies each of the routines into an executable file for the application program. The linker also provides each of the routines with information identifying the locations of other routines so that the routines can access each other. The executable file can then be loaded into the memory of a computer such that the application program can be executed by the computer according to the instructions in the routines.
A dynamic link library (DLL) is an executable module or routine containing services that application programs can call to perform useful tasks, e.g., directory searches, login commands, searching functions, character string manipulations, etc. DLLs exist primarily to provide services to application programs. These libraries play an important role in operating systems such as Windows and OS/2, which use them to make their services and resources available to application programs.
DLLs are similar to run-time libraries. The main difference between DLLs and run-time libraries is that DLLs are linked with the application program at run time, that is, when the computer is executing the application program, not when the application program files are linked with the linker. Linking a library with an application program at run time is referred to as dynamic linking; linking a library with an application program by using the linker is referred to as static linking. The discussion below focuses on the OS/2 operating system, but those of ordinary skill in the art will understand that the following discussion applies equally to other operating systems, such as Windows, which utilize DLLs.
In order to access a DLL at run time, the application program must be able to obtain information indicating where to find the DLL. One method provided by operating systems utilizing DLLs is to use an import library, or reference library, which contains information regarding where to locate the DLL at run time. During linking, the linker uses statically linked reference libraries to resolve references to external services. As noted above, when an application program desires a service from a static link library, the linker copies the code for that service into the application program's executable file. When the application program desires a service run from a DLL, however, the linker does not copy any code from the DLL. Instead, the linker searches all defined import or reference libraries to find one that contains the necessary information regarding location of the DLL. The linker copies the necessary information from the reference or import library to create a dynamic link between the executing application program and the DLL.
When a process loads under the OS/2 operating system, all DLLs that the process references, either directly or indirectly, are also loaded. DLLs referencing other DLLs form a reference tree, the entire set of which gets loaded before the first instruction of the main program is executed. A typical DLL is only partially loaded. The remainder of the DLL is loaded on its first reference. The minimum DLL load requires some initialization in the operating system and the execution of the DLL's initialization routine. Any code or data referenced by the initialization routine, including the initialization routine itself, is loaded. For some DLLs, the dynamic link load time is quite expensive in terms of time, particularly if the DLL is very large or the DLL's initialization routine is very long.
Referring to FIG. 1, the several steps described above are depicted. The source program or application code 50, in a high level language such as C language, and DLL function prototypes 55 are processed by a compiler 60, producing an object code module 65. The object code module 65 is statically linked by a linker 70 to a DLL reference library 75 which resolves external references to the DLL to produce a load module 80 containing the application's executable file ready for loading and running in a computer.
The problem with load time initialization as described above is that frequently a process does not call the DLLs that it references. Therefore, the DLLs which are referenced but not called are unnecessarily loaded. When the load time of the unnecessary DLLs is a large percentage of the total execution time, the performance of the process degrades significantly.
There is a method utilized in the OS/2 operating system to prevent the unnecessary loading of DLLs. The method is called demand loading and is implemented using a well-known documented set of OS/2 operating system APIs (Application Program Interfaces). Demand loading is the process of delaying the load of a DLL until it is actually called. If a DLL is demand loaded but never demanded or called, it is effectively not loaded, and the cost of loading it is saved.
It will be appreciated that demand loading is utilized in other operating systems and creates problems similar to those encountered with the OS/2's demand loading technique.
The problem with the OS/2's demand loading technique is that it is difficult to use, particularly if a program has initially been written without it. It requires the programmer to replace a call to a target dynamic link with a load check, a couple of OS/2 demand loading APIs, and error recovery should the OS/2 APIs fail. The following C-like code illustrates this difficulty:
______________________________________ BEFORE (with no demand loading): x( ) /* call function X( );*/ AFTER (with OS/2's default demand loading): static funcptr x; if (x=0) /* if entry point address has not loaded */ { if(/DLL.sub.-- to.sub.-- load.sub.-- is.sub.-- not.sub.-- loaded) /* if DLL has not been loaded */ { rc = DosLoadModule (DLL.sub.-- to.sub.-- Load); /* load the DLL */ if (rc) { Do.sub.-- error.sub.-- recovery( ); } DLL.sub.-- to.sub.-- load.sub.-- is.sub.-- not.sub.-- loaded = FALSE; } /*obtain the address of entry point x( ) */ rc = DosQueryProcAddr (entry point identifier - either a name or ordinal number); if (rc) { Do.sub.-- error.sub.-- recovery( ); } *x( ); /* call function x indirectly */ ______________________________________
FIG. 2 illustrates the building of an application program with demand loading of DLLs directly coded into the application program. The application program or code 150 is modified to call the demand loading system calls from the operating system's APIs and call the DLL functions indirectly through function pointers as illustrated above in the C-like code. The DLL function prototypes 155 are modified to specify that the functions are called indirectly. The modified application code 150 and modified DLL function prototypes 155 are then compiled by compiler 160 to produce object code module 165. Object code module 165 is then linked by linker 170 to form a load module containing executable application code 180.
Given that demand loading is the solution to the load time initialization problem, what is needed is an alternative to direct coding of the OS/2 operating system's demand loading APIs.